#include "web_server.h"
#include "logger/logger.h"
#include "timer/timer_manager.h"
#include <arpa/inet.h>
#include <errno.h>
#include <fcntl.h>
#include <filesystem>
#include <string.h>
#include <sys/socket.h>

#define ENABLE_MULTITHREAD 1

namespace zh
{
    int WebServer::s_RequestWaitQueueSize = 5;

    WebServer::WebServer()
    {
        // pass
    }

    WebServer::~WebServer()
    {
        clear();
    }

    static std::string getWorkDirectory()
    {
        std::string workRoot = std::filesystem::current_path(); // getcwd(nullptr, 256);
        LOG_INFO("work root: {}, len:{}", workRoot, workRoot.length());
        return workRoot;
    }

    void WebServer::initialize(int port)
    {
        HttpConnection::s_sourceDirectory = getWorkDirectory() + "/resources/";
        HttpConnection::s_isEdgeTrigger   = true;

        if (initSocket(port))
        {
            m_isRunning    = true;
            m_timeout      = 6000; // ms
            m_timerManager = std::make_shared<TimerManager>();
            m_taskRunner   = std::make_shared<ThreadPool>(5);
        }
    }

    void WebServer::clear()
    {
        close(m_serverSocket);
        m_isRunning = false;
    }

    static void setNoBlockingMode(int fd)
    {
        int flag = fcntl(fd, F_GETFL, 0);
        fcntl(fd, F_SETFL, flag | O_NONBLOCK);
    }

    void WebServer::start()
    {
        LOG_INFO("WebServer start");
        while (m_isRunning)
        {
            if (m_timeout > 0)
            {
                // pass
                if (m_timerManager != nullptr)
                {
                    m_timerManager->run();
                    m_timeout = m_timerManager->getNextHandleTime();
                }
            }
            int eventCount = m_epoller.wait(m_timeout);
            if (eventCount == -1)
            {
                LOG_ERROR("epoll wait error");
                m_isRunning = false;
                break;
            }

            for (int i = 0; i < eventCount; ++i)
            {
                int fd = m_epoller.getEventFD(i);
                if (fd == m_serverSocket)
                {
                    bool ok = connectClient();
                }
                else
                {
                    uint32_t events = m_epoller.getEvents(i);

                    auto needReadData              = [=]() -> bool { return events & EPOLLIN; };
                    auto needWriteData             = [=]() -> bool { return events & EPOLLOUT; };
                    auto needCloseClientConnection = [=]() -> bool {
                        return events & (EPOLLRDHUP | EPOLLHUP | EPOLLERR);
                    };

                    if (needReadData())
                    {
                        readData(fd);
                    }
                    else if (needWriteData())
                    {
                        writeData(fd);
                    }
                    else if (needCloseClientConnection())
                    {
                        disconnectClient(fd);
                    }
                    else
                    {
                        // pass
                    }
                }
            }
        }
    }

    bool WebServer::initSocket(int port)
    {
        m_clientEvent  = EPOLLONESHOT | EPOLLRDHUP | EPOLLET;
        m_serverEvent  = EPOLLRDHUP | EPOLLET;
        m_port         = port;
        m_serverSocket = socket(PF_INET, SOCK_STREAM, 0);
        if (m_serverSocket == -1)
        {
            LOG_ERROR("Failed to creat a server socket");
            return false;
        }
        // server reuse address
        int option = 1;
        setsockopt(m_serverSocket, SOL_SOCKET, SO_REUSEADDR, static_cast<void*>(&option), sizeof(option));

        struct sockaddr_in serverAddress;
        memset(&serverAddress, 0, sizeof(serverAddress));
        serverAddress.sin_family      = AF_INET;
        serverAddress.sin_addr.s_addr = htonl(INADDR_ANY);
        serverAddress.sin_port        = htons(m_port);

        if (bind(m_serverSocket, (struct sockaddr*)(&serverAddress), sizeof(serverAddress)) == -1)
        {
            LOG_ERROR("Faild to bind an address");
            return false;
        }

        if (listen(m_serverSocket, s_RequestWaitQueueSize) == -1)
        {
            LOG_ERROR("Faild to listen client connect");
            return false;
        }

        m_epoller.addFD(m_serverSocket, m_serverEvent | EPOLLIN);

        return true;
    }

    bool WebServer::connectClient()
    {
        struct sockaddr_in clientAddress;
        socklen_t          addressSize = sizeof(clientAddress);

        // do
        {
            int clientSocket = accept(m_serverSocket, (struct sockaddr*)(&clientAddress), &addressSize);
            if (clientSocket == -1)
            {
                LOG_ERROR("accept error");
                return false;
            }
            // else
            {
                auto userPtr = std::make_shared<HttpConnection>();
                userPtr->initialize(clientSocket, clientAddress);
                m_userMap[clientSocket] = userPtr;
                setNoBlockingMode(clientSocket);
                m_epoller.addFD(clientSocket, m_clientEvent | EPOLLIN);
                LOG_INFO("connectClient: {}", clientSocket);
                if (m_timeout > 0)
                {
                    int timerID = m_timerManager->addTimer(m_timeout,
                                                           std::bind(&WebServer::disconnectClient, this, clientSocket));
                    m_timerMap.emplace(clientSocket, timerID);
                }
            }
        } // while (m_clientEvent & EPOLLET);
        return true;
    }

    bool WebServer::disconnectClient(int clientSocket)
    {
        if (auto client = getClient(clientSocket))
        {
            LOG_INFO("disconnectClient: {}", clientSocket);
            client->close();
            m_epoller.deleteFD(clientSocket);
            m_timerMap.erase(clientSocket);
            m_userMap.erase(clientSocket);
            return true;
        }
        return false;
    }

    HttpConnectionSharedPtr WebServer::getClient(int clientSocket) const
    {
        auto it = m_userMap.find(clientSocket);
        return (it != m_userMap.end()) ? it->second : nullptr;
    }

    bool WebServer::resetTimerTask(int clientSocket)
    {
        if (auto client = getClient(clientSocket))
        {
            int timerID = m_timerMap[clientSocket];

            m_timerManager->removeTimer(timerID);
            timerID = m_timerManager->addTimer(m_timeout, std::bind(&WebServer::disconnectClient, this, clientSocket));
            m_timerMap[clientSocket] = timerID;

            return true;
        }
        return false;
    }

    bool WebServer::readData(int clientSocket)
    {
        if (resetTimerTask(clientSocket))
        {
#ifndef ENABLE_MULTITHREAD
            onRead(clientSocket);
#else
            m_taskRunner->submitTask(std::bind(&WebServer::onRead, this, clientSocket));
#endif
            return true;
        }
        return false;
    }

    bool WebServer::writeData(int clientSocket)
    {
        if (resetTimerTask(clientSocket))
        {
#ifndef ENABLE_MULTITHREAD
            onWrite(clientSocket);
#else
            m_taskRunner->submitTask(std::bind(&WebServer::onWrite, this, clientSocket));
#endif

            return true;
        }
        return false;
    }

    void WebServer::onRead(int clientSocket)
    {
        if (auto client = getClient(clientSocket))
        {
            int errorNo = 0;
            int length  = client->readBuffer(errorNo);
            LOG_INFO("length: {}, error: {}", length, errorNo);
            if (length <= 0 && errorNo != EAGAIN)
            {
                disconnectClient(clientSocket);
                return;
            }
            onProcess(client);
        }
    }

    void WebServer::onWrite(int clientSocket)
    {
        if (auto client = getClient(clientSocket))
        {
            int errorNo = 0;
            int length  = client->writeBuffer(errorNo);
            LOG_INFO("length: {}, error: {}", length, errorNo);
            if (client->getWriteBytes() == 0)
            {
                LOG_DEBUG("client {}: getWriteBytes is 0", clientSocket);
                if (client->isKeepAlive())
                {
                    LOG_INFO("client {}: isKeepAlive", clientSocket);
                    onProcess(client);
                    return;
                }
            }
            if (length < 0)
            {
                if (errorNo == EAGAIN)
                {
                    m_epoller.modifyFD(clientSocket, m_clientEvent | EPOLLOUT);
                }
                return;
            }
            disconnectClient(clientSocket);
        }
    }

    void WebServer::onProcess(HttpConnectionSharedPtr client)
    {
        assert(client != nullptr);
        if (client->handleConnection())
        {
            m_epoller.modifyFD(client->getFD(), m_clientEvent | EPOLLOUT);
        }
        else
        {
            m_epoller.modifyFD(client->getFD(), m_clientEvent | EPOLLIN);
        }
    }
} // namespace zh